﻿using System;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using System.Windows.Forms;

namespace GnuPGNotepad
{
    /// <summary>
    /// Provides access to Private and Public keys found in the local key store
    /// </summary>
    public class GPGKeys
    {
        #region constructor
        public GPGKeys()
        {
            // load private and public keys during intialization of GPGKeys
            //this.PrivateKey = GetPrivateKey();
            //this.PublicKeys = GetPublicKeys();
        }
        #endregion // constructor
        
        #region public declarations
        public bool IsDisposed { get; private set; }
        public GPGKey PrivateKey { get; private set; }
        public List<GPGKey> PublicKeys { get; private set; }
        #endregion // public declarations
        
        #region private declarations
        private static Settings Config = new Settings();
        #endregion // private declarations

        #region public functions

        /// <summary>
        /// Returns the first private key in keyring
        /// </summary>
        public static GPGKey GetPrivateKey()
        {
            List<GPGKey> keys = GetPrivateKeys();

            // return null if empty, else return first key
            return (null == keys) ? null : keys[0];
        }

        /// <summary>
        /// Returns a collection of private keys found in the local keyring
        /// </summary>
        /// <returns></returns>
        public static List<GPGKey> GetPrivateKeys()
        {
            string response = null;
            using (App app = new App())
            {
                app.Arguments = "--list-secret-keys --with-colons --with-fingerprint";
                app.Run();

                // if bad exit return null value
                if (app.ExitCode != 0)
                    return null;

                response = app.Response;
            }

            // if empty response return null
            return string.IsNullOrEmpty(response) ? null : parseRawResponse(response);
        }

        /// <summary>
        /// returns a collection of public keys found in the local keyring
        /// </summary>
        /// <returns></returns>
        public static List<GPGKey> GetPublicKeys()
        {
            string response = null;
            using (App app = new App())
            {
                app.Arguments = "--list-keys --with-colons --with-fingerprint";
                app.Run();

                // if bad exit return null value
                if (app.ExitCode != 0)
                    return null;
            }

            // if empty response return null
            return string.IsNullOrEmpty(response) ? null : parseRawResponse(response);
        }

        /// <summary>
        /// Check if a single uid is found in local keyring
        /// </summary>
        /// <returns>
        /// Returns true/false if UID is found in local key ring
        /// returns the raw output of the --list-keys command or, 
        /// if error returns the exception message
        /// </returns>
        public static bool UIDExists(string uid, out string response)
        {
            response = null;

            try
            {
                if (string.IsNullOrEmpty(uid))
                    throw new ArgumentNullException("uid");

                using (App app = new App())
                {
                    app.Arguments = string.Format("--list-keys {0}", uid);
                    app.Run();

                    // if bad exit return null value
                    if (app.ExitCode != 0)
                        return false;

                    response = app.Response;
                    return true;
                }


            }
            catch (Exception ex)
            {
                Utils.ShowMessage(ex.Message, MessageBoxIcon.Error);
                response = ex.Message;
                return false;
            }
        }

        /// <summary>
        /// Check if an array of uids are found in local key ring
        /// </summary>
        /// <returns>
        /// returns true/false for locating all UIDs in array
        /// if uid missing then returns first uid that was not found in the search
        /// </returns>
        public static bool UIDExists(string[] uids, out string uid)
        {
            uid = null;
            string response = null;

            foreach (string entry in uids)
            {
                // a single lookup failure for any uids returns false
                if (!UIDExists(entry, out response))
                {
                    uid = entry;
                    return false;
                }
            }
            return true;
        }

        #endregion // public functions

        #region private functions
        private static List<GPGKey> parseRawResponse(string response)
        {
            try
            {
                if (string.IsNullOrEmpty(response))
                    throw new ArgumentNullException("stream");

                // prime variables for use within loop
                List<GPGKey> keys = new List<GPGKey>();
                GPGKey current = null;
                bool keyFound = false;

                // split into individual lines and loop through each line
                foreach (string line in response.Split(new char[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries))
                {
                    // first line in group for either private or public keys
                    if (Regex.IsMatch(line, @"^(sec|pub).*", RegexOptions.IgnoreCase & RegexOptions.Compiled))
                    {
                        // toggle grouping flag on
                        keyFound = true;

                        // prime the Uid and EmailAddress arrays
                        current = new GPGKey { UIDs = new List<string>(), EmailAddresses = new List<string>() };

                        // just grab the entire line for now
                        if (line.StartsWith("sec", StringComparison.OrdinalIgnoreCase))
                            current.Sec = line;
                        else if (line.StartsWith("pub", StringComparison.OrdinalIgnoreCase))
                            current.Pub = line;
                    }

                    if (keyFound)
                    {
                        // just grab the entire line for now
                        if (line.StartsWith("fpr", StringComparison.OrdinalIgnoreCase))
                            current.Fingerprint = line;

                        // just grab the entire line for now
                        if (line.StartsWith("rvk", StringComparison.OrdinalIgnoreCase))
                            current.Revoke = line;

                        // parse the uid line
                        if (line.StartsWith("uid", StringComparison.OrdinalIgnoreCase))
                        {
                            // split uid line by colon
                            string[] uidLine = line.Split(new char[] { ':' }, StringSplitOptions.None);

                            // get the UserID info based on the placement within the key fields
                            string userID = uidLine[(int)GPGKey.KeyFields.UserID];

                            // if userID not empty then add to current key fields
                            if (!string.IsNullOrEmpty(userID))
                            {
                                // grab the UserID field from the uid line
                                current.UIDs.Add(userID);

                                // might as well grab email addresses from all UserID's
                                Regex regExEmailExp = new Regex(@"(?<=<).*(?=>)", RegexOptions.Compiled);
                                if (regExEmailExp.IsMatch(userID))
                                    current.EmailAddresses.Add(regExEmailExp.Match(userID).ToString());
                            }
                        }

                        // final line in group
                        if (Regex.IsMatch(line, @"^(sub|ssb).*", RegexOptions.IgnoreCase & RegexOptions.Compiled))
                        {
                            // just grab the entire line for now
                            if (line.StartsWith("ssb", StringComparison.OrdinalIgnoreCase))
                                current.Ssb = line;
                            else if (line.StartsWith("sub", StringComparison.OrdinalIgnoreCase))
                                current.Sub = line;

                            // add current key into key list
                            keys.Add(current);

                            // toggle grouping flag off
                            keyFound = false;
                        }
                    }
                }

                // return keys collection
                return keys;
            }
            catch (Exception ex)
            {
                // display something bad happened
                Utils.ShowMessage(ex.Message, MessageBoxIcon.Error);
                return null;
            }
        }

        #endregion // private functions

        #region IDisposable Members
        /// <summary>
        /// Dispose of object
        /// </summary>
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        /// <summary>
        /// Dispose of objects in class
        /// </summary>       
        protected virtual void Dispose(bool disposing)
        {
            if (!IsDisposed)
                if (disposing)
                {
                    // dispose of objects
                    PrivateKey = null;
                    PublicKeys = null;
                    Config = null;

                    // mark disposed
                    IsDisposed = true;
                }
        }

        /// <summary>
        /// Destructor method class
        /// </summary>
        ~GPGKeys()
        {
            Dispose(false);
        }
        #endregion // IDisposable Members

    }
}
